package ownserver;

import java.nio.ByteBuffer;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.Iterator;

import base.OPCODES;

import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.util.ScalableList;

import orders.*;

public class Player extends Commander implements ClientSessionListener {

	/** The version of the serialized form of this class. */
    private static final long serialVersionUID = 1L;
    
    /** The {@link Logger} for this class. */
    private static final Logger logger =
        Logger.getLogger(Player.class.getName());
    
    /** The prefix for player bindings in the {@code DataManager}. */
    protected static final String SHIP_BIND_PREFIX = "Ship.";

    /** The {@code ClientSession} for this player, or null if logged out. */
    private ManagedReference<ClientSession> currentSessionRef = null;
    
    /**
     * Find or create the player object for the given session, and mark
     * the player as logged in on that session.
     *
     * @param session which session to find or create a player for
     * @return a player for the given session
     */
    public static Player loggedIn(ClientSession session, ManagedReference<World> worldRef, int id) {
        String playerBinding = PLAYER_BIND_PREFIX + session.getName();

        // try to find player object, if non existent then create
        DataManager dataMgr = AppContext.getDataManager();
        Player player;
        
        World world = worldRef.get();

        try {
            player = (Player) dataMgr.getBinding(playerBinding);
            player.setSession(session);
            player.informClientID();
            world.joinToShipChannels(player);
        } catch (NameNotBoundException ex) {
            // this is a new player
            player = new Player(playerBinding, worldRef, id, session);
            logger.log(Level.INFO, "New player created: {0}", player);
            dataMgr.setBinding(playerBinding, player);
            player.informClientID();
            world.addPlayer(player);
            world.createInitialShips(player);
        }
        
        return player;
    }
    
    /**
     * Lähettää pelaajalle tämän id-tunnisteen.
     * 
     */
    public void informClientID() {
    	ClientSession session = getSession();
    	ByteBuffer buffer = Messages.createUserIDPkt(userid);
    	session.send(buffer);
    }
    
    /**
     * Creates a new {@code Player} with the given name.
     *
     * @param n the name of this player
     */
    protected Player(String n, ManagedReference<World> worldRef, int id, ClientSession session) {
    	super(n, worldRef, id);

        this.setSession(session);
    }
    
    /**
     * Returns the session for this listener.
     * 
     * @return the session for this listener
     */
    public ClientSession getSession() {
        if (currentSessionRef == null) {
            return null;
        }

        return currentSessionRef.get();
    }

    /**
     * Mark this player as logged in on the given session.
     *
     * @param session the session this player is logged in on
     */
    protected void setSession(ClientSession session) {
        DataManager dataMgr = AppContext.getDataManager();
        dataMgr.markForUpdate(this);

        currentSessionRef = dataMgr.createReference(session);

        logger.log(Level.INFO,
            "Set session for {0} to {1}",
            new Object[] { this, session });
    }
    
    
    /** {@inheritDoc} */
    public void disconnected(boolean graceful) {
    	World world = worldRef.get();
    	world.removeFromChannels(this);
    	currentSessionRef = null; // TODO: voidaanko olemassaoleva referenssi vaan heitt�� m�keen, vai pit�isik� toimia jotenkin muuten?
        logger.log(Level.INFO, "Disconnected: {0}", this);
    }

    /** {@inheritDoc} */
    public void receivedMessage(ByteBuffer message) {
        Byte command = message.get();

        logger.log(Level.INFO,
            "{0} received command: {1}",
            new Object[] { this, command }
        );

        if (command == OPCODES.ORDER_MOVE_SHIP.getByte()) {
            OrderMoveShip order = Messages.decodeShipMoveOrder(message);
            this.handleMoveShipOrder(order);
        } else {
            logger.log(Level.WARNING,
                "{0} unknown command: {1}",
                new Object[] { this, command }
            );
            // We could disconnect the rogue player at this point.
            //currentSession.disconnect();
        }
    }
    
    public void addToOwnedShips(ServerShip ship)
    {
    	ScalableList<ServerShip> ships = ownedShipsRef.getForUpdate();
    	ships.add(ship);
    }
    
    protected void handleMoveShipOrder(OrderMoveShip order)
    {
    	logger.log(Level.INFO, "Relaying move order to the ship");
    	
    	ScalableList<ServerShip> ships = ownedShipsRef.get();
    	
    	Iterator <ServerShip> itr = ships.iterator();
    	// omassa säiliössä pitäisi olla vain omia laivoja, joten ei tarvitse tehdä tarkistelua siitä,
    	// omistaako käskyn antaja laivan jota se käskyttää.
    	
    	while (itr.hasNext()) {
    		ServerShip s = itr.next();
    		if (s != null) {
    			if (s.getGuid() == order.guid) {
    				s.setMoveTarget(order.x, order.y);
    				// TODO: voisiko tässä kohtaa lopettaa iteroinnin?
    			}
    		}
    	}
    }
    
    /** {@inheritDoc} */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(getName());
        buf.append('@');
        if (getSession() == null) {
            buf.append("null");
        } else {
            buf.append(currentSessionRef.getId());
        }
        return buf.toString();
    }
}
